/*
 * Copyright 2011 es-planning Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jp.arctos.ursus.extension.net;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;

/**
 * Class for handling the datagram service.
 * Handles generating sessions and controlling asynchronous handling of UDP
 *
 */
public class DatagramService extends Service {

    /** Event handler. */
    DatagramEventHandler eventHandler;

    /**
     * Constructor.
     * Generate an instance with the default configuration
     * 
     * @throws IOException 
     */
    public DatagramService() throws IOException {
        this(new Config());
    }

    /**
     * Constructor.
     * Generate an instance with the specified configuration
     * 
     * @param config Configuration
     * @throws IOException
     */
    public DatagramService(Config config) throws IOException {
        super(config);
    }

    /**
     * Configure handler for asynchronous events.
     * 
     * @param eventHandler Event handler
     */
    public void setEventHandler(DatagramEventHandler eventHandler) {
        this.eventHandler = eventHandler;
    }

    /**
     * Generate a datagram session.
     * It will be automatically bound to the local host address.
     * 
     * @return Session handler
     * @throws IOException
     */
    public synchronized DatagramSessionHandler open() throws IOException {
        return open(null);
    }

    /**
     * Generate a datagram session.
     * It will be bound to the specified local host address.
     * 
     * @param localAddress Local host addresss
     * @return Session handler
     * @throws IOException
     */
    public synchronized DatagramSessionHandler open(SocketAddress localAddress) throws IOException {
        if (isSessionCreated.get() == true) throw new IllegalStateException();

        DatagramChannel channel = null;
        try {
            channel = DatagramChannel.open();
            channel.socket().setReuseAddress(config.getReuseAddress());
            channel.socket().setReceiveBufferSize(config.getReceiveBufferSize());
            channel.socket().bind(localAddress);

            // TODO channel configuration.

            channel.configureBlocking(false);
            DatagramSession session = new DatagramSession(this, channel, selectorHandler, config);
            selectorHandler.register(channel, SelectionKey.OP_READ, session);
            selectorHandler.start();

            sessions.add(session);
            isSessionCreated.set(true);

            return session.getSessionHandler();

        } catch (IOException ioe) {
            if (channel != null) {
                channel.close();
            }
            throw ioe;
        }
    }

    /**
     * Received a "session close". // sessionが終了されたことを通知された場合の処理
     * 
     * @param session Session
     */
    @Override
    void sessionClosed(Session session) {
        sessions.remove(session);
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    void doShutdown() {
        selectorHandler.close();
        for (Session session : sessions) {
        	DatagramSession datagramSession = (DatagramSession) session;
            if (session.closeSession()) {
                if (eventHandler != null) {
                    eventHandler.sessionClosed(datagramSession.getSessionHandler(), datagramSession.getWriteQueue());
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    void onReadable(SelectionKey key) {
        Object attachment = key.attachment();
        if (attachment instanceof DatagramSession) {
            DatagramSession session = (DatagramSession) attachment;
            try {
                ReceivedContext context = session.read();
                if (eventHandler != null) {
                    eventHandler.received(session.getSessionHandler(), context);
                }
            } catch (Exception e) {
                if (session.closeSession()) {
                    if (eventHandler != null) {
                        eventHandler.sessionClosed(e, session.getSessionHandler(), session.getWriteQueue());
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    void onSelectionError(Exception reason) {
        shutdown();
    }

    /**
     * {@inheritDoc}
     * 
     */
    @Override
    void onWritable(SelectionKey key) {
        Object attachment = key.attachment();
        if (attachment instanceof DatagramSession) {
            DatagramSession session = (DatagramSession) attachment;
            try {
                SendRequest request = session.write();
                if (request != null) {
                    if (eventHandler != null) {
                        eventHandler.sent(session.getSessionHandler(), request);
                    }
                }
            } catch (Exception e) {
                if (session.closeSession()) {
                    if (eventHandler != null) {
                        eventHandler.sessionClosed(e, session.getSessionHandler(), session.getWriteQueue());
                    }
                }
            }
        }
    }
}
